package com.mofum.msdom.excel.writer.impl;

import com.mofum.msdom.excel.builder.RowBuilder;
import com.mofum.msdom.excel.builder.SheetBuilder;
import com.mofum.msdom.excel.builder.StringCellBuilder;
import com.mofum.msdom.excel.builder.StyleBuilder;
import com.mofum.msdom.excel.collections.FieldExcelConvertMap;
import com.mofum.msdom.excel.collections.FieldMPColumnMap;
import com.mofum.msdom.excel.collections.IntegerStringMap;
import com.mofum.msdom.excel.collections.SheetContextMap;
import com.mofum.msdom.excel.collections.type.SheetContext;
import com.mofum.msdom.excel.constant.ExcelConfig;
import com.mofum.msdom.excel.constant.ExcelValue;
import com.mofum.msdom.excel.constant.ValueLevel;
import com.mofum.msdom.excel.converter.ExcelConverter;
import com.mofum.msdom.excel.metadata.*;
import com.mofum.msdom.excel.utils.StringUtils;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.util.List;

/**
 * 多类型Excel写入器
 *
 * @author 1615690513@qq.com
 * @since 2018/11/26 0026 11:27
 */
public class MultiTypeExcelWriterImpl<Data> extends BaseWriterAdapter<Data> {

    public static Logger logger = LoggerFactory.getLogger(MultiTypeExcelWriterImpl.class);

    /**
     * 输出流
     */
    protected OutputStream outputStream;

    /**
     * 工作簿
     */
    protected Workbook workbook;

    /**
     * 工作表上下文Map
     */
    protected SheetContextMap<SheetContext> sheetContextMap;

    /**
     * 主要数据类型
     */
    protected Class<Data> primaryClass;

    /**
     * 主要数据类型的索引
     */
    protected Integer primarySheetContextIndex;

    /**
     * 当前工作表
     */
    protected Integer currentSheetIndex;

    /**
     * 存放内存的记录条数
     */
    protected int cache;

    /**
     * Excel配置项
     */
    protected ExcelConfig excelConfig;

    @Override
    public MultiTypeExcelWriterImpl cache(int cache) {
        //设置当前工作薄的缓存
        this.setCache(cache);
        return this;
    }

    @Override
    public MultiTypeExcelWriterImpl start(OutputStream output) {
        //导出前的准备工作
        this.setOutputStream(output);
        open();
        return this;
    }

    @Override
    public MultiTypeExcelWriterImpl end() {
        if (outputStream == null) {
            return this;
        }

        try {
            //将数据写入到输出流中
            workbook.write(outputStream);
        } catch (IOException e) {
            logger.error(e.getMessage(), e);
            throw new RuntimeException(e.getMessage(), e);
        }

        try {
            outputStream.close();
            workbook.close();
        } catch (IOException e) {
            logger.error(e.getMessage(), e);
            throw new RuntimeException(e.getMessage(), e);
        }
        release();
        return this;
    }

    @Override
    public MultiTypeExcelWriterImpl dataType(Class<Data> dataClass) {
        //注册主要的数据类型
        this.setPrimaryClass(dataClass);
        return this;
    }

    @Override
    public MultiTypeExcelWriterImpl open() {
        //打开工作薄流
        if (workbook != null) {
            end();
        }
        workbook = new SXSSFWorkbook(cache);
        return this;
    }

    @Override
    public MultiTypeExcelWriterImpl release() {
        if (workbook == null) {
            return this;
        }

        try {
            workbook.close();
        } catch (IOException e) {
            logger.error(e.getMessage(), e);
            throw new RuntimeException(e.getMessage(), e);
        }

        if (workbook instanceof SXSSFWorkbook) {
            ((SXSSFWorkbook) workbook).dispose();
        }

        workbook = null;
        //释放工作薄流
        return this;
    }

    @Override
    public MultiTypeExcelWriterImpl addSheet(MPSheet sheet) {
        SheetContext sheetContext = new SheetContext();

        sheetContext.setMpSheet(sheet);

        addSheetContext(sheetContext);
        return this;
    }

    @Override
    public MultiTypeExcelWriterImpl addSheet(Class<?> dataClass, MPSheet sheet) {

        SheetContext sheetContext = new SheetContext();

        sheetContext.setMpSheet(sheet);

        sheetContext.setDataClass(dataClass);

        addSheetContext(sheetContext);

        return this;
    }

    @Override
    public MultiTypeExcelWriterImpl addSheets(MPSheet... sheets) {
        for (MPSheet mpSheet : sheets) {
            addSheet(mpSheet);
        }
        return this;
    }

    @Override
    public MultiTypeExcelWriterImpl addSheets(Class<?> dataClass, MPSheet... sheets) {
        for (MPSheet mpSheet : sheets) {
            addSheet(dataClass, mpSheet);
        }
        return this;
    }

    @Override
    public MultiTypeExcelWriterImpl sheets(MPSheet... sheets) {
        addSheets(sheets);
        return this;
    }

    @Override
    public <T> MultiTypeExcelWriterImpl write(List<T> datas) {
        if (datas == null) {
            return this;
        }
        writeRowsData(datas);
        return this;
    }

    @Override
    public <T> MultiTypeExcelWriterImpl write(int sheetIndex, List<T> datas) {
        SheetContext sheetContext = getSheetContextMap().get(sheetIndex);
        if (sheetContext != null) {
            sheetIndex(sheetIndex);
            return write(datas);
        }
        return this;
    }

    @Override
    public MultiTypeExcelWriterImpl sheetIndex(int index) {
        this.setCurrentSheetIndex(index);
        return this;
    }

    @Override
    public MultiTypeExcelWriterImpl addMergeRegion(MPMergeRange range) {
        SheetContext sheetContext = getCurrentSheetContext();
        Sheet sheet = sheetContext.getSheet();
        sheet.addMergedRegion(new CellRangeAddress(range.getStartRow(), range.getEndRow(), range.getStartCol(), range.getEndCol()));
        return this;
    }

    @Override
    public MultiTypeExcelWriterImpl addMergeRegion(CellRangeAddress range) {
        SheetContext sheetContext = getCurrentSheetContext();
        Sheet sheet = sheetContext.getSheet();
        sheet.addMergedRegion(range);
        return this;
    }

    @Override
    public MultiTypeExcelWriterImpl addMergeRegion(int firstRow, int lastRow, int firstCol, int lastCol) {
        SheetContext sheetContext = getCurrentSheetContext();
        Sheet sheet = sheetContext.getSheet();
        sheet.addMergedRegion(new CellRangeAddress(firstRow, lastRow, firstCol, lastCol));
        return this;
    }

    /**
     * 写多行数据
     *
     * @param datas 数据
     */
    private <T> void writeRowsData(List<T> datas) {

        SheetContext sheetContext = getCurrentSheetContext();

        if (sheetContext == null) {
            return;
        }

        Sheet sheet = this.createSheet(sheetContext.getMpSheet());

        for (T data : datas) {
            sheetContext.setCurrentRowIndex((sheetContext.getCurrentRowIndex() + 1));
            writeSingeRowData(sheet, data);
        }

    }

    /**
     * 创建 工具表格
     *
     * @param mpSheet 工作表信息
     *                工作表格
     */
    private Sheet createSheet(MPSheet mpSheet) {

        SheetContext sheetContext = getCurrentSheetContext();

        Sheet sheet = sheetContext.getSheet();
        if (sheet != null) {
            return sheet;
        }

        SheetBuilder sheetBuilder = new SheetBuilder(getWorkbook());
        sheetBuilder.newSheet(mpSheet.getName());


        FieldMPColumnMap columnMap = sheetContext.getColumnMap();

        if (mpSheet.isAutoSize()) {
            //自动设置大小
            for (Field field : columnMap.keySet()) {
                MPColumn mpColumn = columnMap.get(field);
                sheetBuilder.trackColumnForAutoSizing(mpColumn.getIndex());
            }
        }

        //设置宽度
        for (Field field : columnMap.keySet()) {
            MPColumn mpColumn = columnMap.get(field);

            if (mpColumn.getWidth() > 0) {
                sheetBuilder.columnWidth(mpColumn.getIndex(), mpColumn.getWidth());
            }
        }

        for (MPMergeRange range : mpSheet.getRanges()) {
            sheetBuilder.mergedRegion(range);
            if (ValueLevel.REPLACE.equals(range.getLevel())) {
                //赋值
                //赋值样式
            }
        }

        sheet = sheetBuilder.build();

        //创建表头
        MPHeaderRow[] headerRows = mpSheet.getHeaderRows();
        if (headerRows != null) {
            sheetContext.setCurrentRowIndex(headerRows.length - 1);

            for (MPHeaderRow mpHeaderRow : headerRows) {
                if (mpHeaderRow.isRequired()) {
                    //创建表头
                    createHeaderRow(mpHeaderRow, sheet);
                }
            }

        }

        sheetContext.setSheet(sheet);

        return sheet;
    }

    private void createHeaderRow(MPHeaderRow mpHeaderRow, Sheet sheet) {
        if (mpHeaderRow.getColumns() == null) {
            return;
        }
        RowBuilder rowBuilder = new RowBuilder(sheet);

        Row row = rowBuilder.newRow(mpHeaderRow.getIndex()).build();

        for (MPHeaderColumn mpHeaderColumn : mpHeaderRow.getColumns()) {

            ExcelValue excelValue = new ExcelValue();
            excelValue.setType(CellType.STRING);
            excelValue.setValue(mpHeaderColumn.getValue());
            MPColumn mpColumn = new MPColumn();

            mpColumn.setIndex(mpHeaderColumn.getIndex());
            mpColumn.setStyles(mpHeaderColumn.getStyles());
            createCellData(excelValue, mpColumn, row);
        }

    }

    /**
     * 写单行数据
     *
     * @param sheet 工作薄
     * @param data  数据
     */
    private <T> void writeSingeRowData(Sheet sheet, T data) {
        if (data == null) {
            return;
        }
        if (sheet == null) {
            return;
        }

        createRow(sheet, data);

    }

    /**
     * 创建行数据
     *
     * @param sheet 工作表
     * @param data  数据
     */
    private <T> void createRow(Sheet sheet, T data) {

        RowBuilder rowBuilder = new RowBuilder(sheet);

        SheetContext sheetContext = getCurrentSheetContext();

        Row row = rowBuilder.newRow(sheetContext.getCurrentRowIndex()).height(sheetContext.getRowHeight()).build();

        FieldMPColumnMap columnMap = sheetContext.getColumnMap();

        FieldExcelConvertMap excelConvertMap = sheetContext.getExcelConvertMap();
        try {

            for (Field field : columnMap.keySet()) {
                MPColumn mpColumn = columnMap.get(field);
                if (mpColumn == null) {
                    continue;
                }
                field.setAccessible(true);

                Object value = field.get(data);
                field.setAccessible(false);

                ExcelConverter excelConverter = excelConvertMap.get(field);
                ExcelValue cellValue = excelConverter.convert(value);

                createCellData(cellValue, mpColumn, row);

            }

        } catch (Exception e) {
            logger.error(e.getMessage(), e);
            throw new RuntimeException(e.getMessage(), e);
        }

    }

    /**
     * 创建Cell 数据
     *
     * @param value    数据值
     * @param mpColumn 列相关信息
     * @param row      行
     */
    private void createCellData(ExcelValue value, MPColumn mpColumn, Row row) {
        StringCellBuilder cellBuilder = new StringCellBuilder(row);
        MPStyle style = null;
        if (mpColumn.getStyles() != null && mpColumn.getStyles().length > 0) {
            style = mpColumn.getStyles()[0];
        }

        cellBuilder.newCell(mpColumn.getIndex()).type(value.getType()).value(value.getValue()).build();

        if (style != null) {
            StyleBuilder styleBuilder = new StyleBuilder(workbook);
            if (value.getDataFormat() != null && "".equals(value.getDataFormat())) {
                styleBuilder.dataFormat(value.getDataFormat());
            }
            CellStyle cellStyle = styleBuilder.mpStyle(style).build();
            cellBuilder.style(cellStyle);
        }

        if (mpColumn.getWidth() <= 0) {
            int width = row.getSheet().getColumnWidth(mpColumn.getIndex());

            int colWidth = StringUtils.stringLength(String.valueOf(value));

            if (colWidth > width) {
                row.getSheet().setColumnWidth(mpColumn.getIndex(), StringUtils.stringLength(String.valueOf(value)));
            }
        }
    }

    public MultiTypeExcelWriterImpl() {
    }

    public MultiTypeExcelWriterImpl(SheetContextMap sheetContextMap, Class<Data> primaryClass, Integer primarySheetContextIndex) {
        this.setSheetContextMap(sheetContextMap);
        this.setPrimaryClass(primaryClass);
        this.setPrimarySheetContextIndex(primarySheetContextIndex);
    }

    public MultiTypeExcelWriterImpl(SheetContext primarySheetContext) {
        registerPrimarySheetContext(primarySheetContext, (Class<Data>) primarySheetContext.getDataClass());
    }

    public MultiTypeExcelWriterImpl(SheetContext primarySheetContext, Class<Data> primaryClass) {
        registerPrimarySheetContext(primarySheetContext, primaryClass);
    }

    /**
     * 注册主要的工作表 上下文内容
     *
     * @param primarySheetContext 主要的工作表上下文内容
     */
    private void registerPrimarySheetContext(SheetContext primarySheetContext, Class<Data> primaryClass) {
        if (this.getSheetContextMap() == null) {
            this.setSheetContextMap(new SheetContextMap());
        }
        primarySheetContext.setDataClass(primaryClass);
        this.getSheetContextMap().put(primarySheetContext.getSheetIndex(), primarySheetContext);
        this.setPrimarySheetContextIndex(primarySheetContext.getSheetIndex());
        this.setPrimaryClass(primaryClass);
    }

    public void addSheetContext(SheetContext sheetContext) {
        if (sheetContext == null) {
            return;
        }

        if (sheetContext.getMpSheet() == null) {
            throw new RuntimeException("Sheet is null!");
        }

        Class<?> dataClass = sheetContext.getDataClass();

        if (dataClass == null) {
            sheetContext.setDataClass(getPrimaryClass());
        }

        FieldExcelConvertMap excelConvertMap = sheetContext.getExcelConvertMap();
        if (excelConvertMap == null) {
            excelConvertMap = this.getPrimarySheetContext().getExcelConvertMap();
            sheetContext.setExcelConvertMap(excelConvertMap);
        }

        FieldMPColumnMap columnMap = sheetContext.getColumnMap();

        if (columnMap == null) {
            columnMap = this.getPrimarySheetContext().getColumnMap();
            sheetContext.setColumnMap(columnMap);
        }

        IntegerStringMap indexMap = sheetContext.getColumnIndexMap();
        if (indexMap == null) {
            indexMap = this.getPrimarySheetContext().getColumnIndexMap();
            sheetContext.setColumnIndexMap(indexMap);
        }

        this.getSheetContextMap().put(sheetContext.getSheetIndex(), sheetContext);
    }

    /**
     * 获得主要的工作表 上下文内容
     */
    public SheetContext getPrimarySheetContext() {
        if (this.getSheetContextMap() == null) {
            return null;
        }
        SheetContext sheetContext = this.getSheetContextMap().get(getPrimarySheetContextIndex());
        return sheetContext;
    }

    /**
     * 获得主要的工作表 上下文内容
     */
    public SheetContext getCurrentSheetContext() {
        if (this.getSheetContextMap() == null) {
            return null;
        }
        SheetContext sheetContext = this.getSheetContextMap().get(getCurrentSheetIndex());

        if (sheetContext == null) {
            sheetContext = getPrimarySheetContext();
            setCurrentSheetIndex(getPrimarySheetContextIndex());
        }

        if (sheetContext == null) {
            throw new RuntimeException("Current sheet does not get sheet context!");
        }

        return sheetContext;
    }

    /**
     * 设置主要的工作表 上下文内容
     */
    public void setPrimarySheetContext(SheetContext primarySheetContext) {
        if (this.getSheetContextMap() == null) {
            this.setSheetContextMap(new SheetContextMap<>());
        }
        setPrimarySheetContextIndex(primarySheetContext.getSheetIndex());
        this.getSheetContextMap().put(primarySheetContextIndex, primarySheetContext);
    }

    /**
     * 设置主要的工作表 上下文内容
     */
    public void setPrimarySheetContext(SheetContext primarySheetContext, Class<Data> primaryClass) {
        if (this.getSheetContextMap() == null) {
            this.setSheetContextMap(new SheetContextMap<>());
        }
        setPrimarySheetContextIndex(primarySheetContext.getSheetIndex());
        this.getSheetContextMap().put(primarySheetContextIndex, primarySheetContext);
        this.setPrimaryClass(primaryClass);
    }

    public OutputStream getOutputStream() {
        return outputStream;
    }

    public void setOutputStream(OutputStream outputStream) {
        this.outputStream = outputStream;
    }

    public Workbook getWorkbook() {
        return workbook;
    }

    public void setWorkbook(Workbook workbook) {
        this.workbook = workbook;
    }

    public SheetContextMap<SheetContext> getSheetContextMap() {
        return sheetContextMap;
    }

    public void setSheetContextMap(SheetContextMap<SheetContext> sheetContextMap) {
        this.sheetContextMap = sheetContextMap;
    }

    public Class<Data> getPrimaryClass() {
        return primaryClass;
    }

    public void setPrimaryClass(Class<Data> primaryClass) {
        this.primaryClass = primaryClass;
    }

    public Integer getPrimarySheetContextIndex() {
        return primarySheetContextIndex;
    }

    public void setPrimarySheetContextIndex(Integer primarySheetContextIndex) {
        this.primarySheetContextIndex = primarySheetContextIndex;
    }

    public int getCache() {
        return cache;
    }

    public void setCache(int cache) {
        this.cache = cache;
    }

    public Integer getCurrentSheetIndex() {
        return currentSheetIndex;
    }

    public void setCurrentSheetIndex(Integer currentSheetIndex) {
        this.currentSheetIndex = currentSheetIndex;
    }

    public ExcelConfig getExcelConfig() {
        if (excelConfig == null) {
            return ExcelConfig.DEFAULT_EXCEL_CONFIG;
        }
        return excelConfig;
    }

    public void setExcelConfig(ExcelConfig excelConfig) {
        this.excelConfig = excelConfig;
    }
}
